DEVELOPMENT SPECIFICATION · FRONTEND + BACKEND

🔐 ConstructQ — RBAC Settings (Web) · Dev Spec · v1.0

เอกสารสเปคการพัฒนาหน้าจอ "บทบาทและสิทธิ์การเข้าถึง" (Role & Access Control) สำหรับทีม Frontend (Next.js 14) และ Backend (Go-Chi v5) — ครอบคลุม Permission Matrix 7×26 · Add/Edit Role modal · Auto/Manual code switch · Audit log · system roles protection · ตัวอย่างโค้ด + DFD + State Sequence Diagram

🏷️ Doc ID: DEVSPEC-RBAC-2026-001 📅 7 มิถุนายน 2569 ⚛️ Next.js 14 App Router 🦫 Go-Chi v5 🐘 PostgreSQL 15 📜 Reference: ConstructQ_RBACSettings.html · Role&Privilege.pdf

📋 สารบัญ · Table of Contents

  1. 1. ภาพรวมและ Tech Stack
  2. 2. Architecture & Route Setup
  3. 3. RBAC Model (7 roles × 26 modules × 5 actions)
  4. 4. Page Layout (Header + Roles + Matrix)
  5. 5. Roles List Section
  6. 6. Permission Matrix Section
  7. 7. Create / Edit Role Modal
  8. 8. Auto/Manual Code Switch
  9. 9. System Roles Protection
  10. 10. Server Actions & API Endpoints
  11. 11. Data Flow Diagram (DFD)
  12. 12. State Sequence Diagram
  13. 13. Validation Reference
  14. 14. Audit Log + Security
  15. 15. Acceptance Criteria
1

🎯 ภาพรวมและ Tech Stack

หน้าจัดการบทบาทผู้ใช้งาน — เข้าถึงเฉพาะ platform_admin + org_admin · path: /[locale]/setting/master/rbac

วัตถุประสงค์

หน้านี้คือศูนย์กลางสำหรับ กำหนดบทบาทผู้ใช้งานและสิทธิ์การเข้าถึงโมดูล ในระบบ ConstructQ · รองรับ 7 system roles + custom roles · ใช้ Permission Matrix 26 modules × 5 actions (R/C/E/D/A) เป็น single source of truth

Tech Stack (Frontend)

  • Framework:Next.js 14 App Router · Server Components first · ครอบ AppShell
  • Language:TypeScript 5.4 (strict mode)
  • Styling:Tailwind 3.4 + CSS variables (Flip7 tokens) · cn() helper
  • Tables:AntD Table v5 (limited use) — สำหรับ Permission Matrix
  • Forms:React Hook Form 7 + Zod 3.23 (Add/Edit Role modal)
  • State:Zustand 4.5 (UI selected role) · SWR สำหรับ roles + modules data
  • Modals:HeadlessUI Dialog หรือ AntD Modal
  • i18n:next-intl 3.x · TH/EN cascade

Tech Stack (Backend)

  • Framework:Go 1.22 + go-chi/v5 (~14 endpoints สำหรับ RBAC)
  • DB:PostgreSQL 15 · tables: roles · modules · permissions
  • Auth:JWT bearer · ตรวจสิทธิ์ผ่าน RequirePerm("m_rbac", action)
  • Audit:AuditMiddleware บันทึก before/after ทุก write
  • Validation:go-playground/validator · custom role code format
⚠️
การเข้าถึงหน้านี้:
· platform_admin — F (R/C/E/D/A) ทุก role · ทุก org
· org_admin — F แต่จำกัดเฉพาะ org ของตัวเอง
· บทบาทอื่น — N (ไม่เห็นเมนูในเลย · ถูกซ่อนจาก Sidebar ผ่าน hasPerm())
ℹ️
เอกสารอ้างอิง:
· CLAUDE.md §7 RBAC
· ConstructQ_RBACSettings.html — HTML prototype
· Document/ConstructQ_Role&Privilege.pdf — official matrix
· User Role.pdf — role description
· ConstructQ_Schema.sql — roles · modules · permissions tables
2

🏗️ Architecture & Route Setup

โครงสร้างไฟล์ · ครอบ AppShell · ใช้ Server Components fetch ครั้งแรก · Client interactivity ใช้ Zustand

File Structure

// src/app/[locale]/(app)/setting/master/rbac/
├── page.tsx                 // Server Component · fetch initial
├── actions.ts               // Server Actions (RBAC mutations)
├── schema.ts                // Zod schemas (Role · Permission)
├── store.ts                 // Zustand UI state
└── components/
    ├── RBACPageClient.tsx     // Top-level client wrapper
    ├── RolesList.tsx         // 7 roles + custom + Add button
    ├── PermissionMatrix.tsx  // 26 modules × 5 actions
    ├── RoleModal.tsx         // Create/Edit role dialog
    ├── CodeSwitcher.tsx      // Auto/Manual code toggle
    └── DeleteRoleConfirm.tsx // Soft-delete confirm

Route & AppShell Wrapping

// src/app/[locale]/(app)/layout.tsx — already wraps AppShell
export default async function AppLayout({ children }: { children: React.ReactNode }) {
  return (
    <AppShell>
      <Sidebar />
      <Topbar />
      <Breadcrumb />
      <main className="content-wrap">{children}</main>
    </AppShell>
  );
}

Page Component (Server)

// page.tsx
import { requirePerm } from '@/lib/rbac/guard';
import { RBACPageClient } from './components/RBACPageClient';

export default async function RBACPage() {
  await requirePerm('m_rbac', 'view');             // 🛡️ guard

  const [roles, modules, permissions] = await Promise.all([
    fetch(`${API}/api/roles`).then(r => r.json()),
    fetch(`${API}/api/modules`).then(r => r.json()),
    fetch(`${API}/api/permissions`).then(r => r.json())
  ]);

  return <RBACPageClient
    initialRoles={roles}
    modules={modules}
    initialPermissions={permissions}
  />;
}

Server vs Client

ComponentTypeReason
page.tsxServerInitial fetch + RBAC guard
RBACPageClientClientstate management สำหรับเลือก role + filter
RolesListClientonClick → update Zustand selectedRoleId
PermissionMatrixClientCheckbox toggles + optimistic update
RoleModalClientReact Hook Form + Zod
actions.tsServer'use server' · mutations
3

🗂️ RBAC Model (7 roles × 26 modules × 5 actions)

โครงสร้างข้อมูลตาม Role&Privilege.pdf · encoded เป็น F/4/3/V/N

7 Roles

Codeชื่อ (TH)ชื่อ (EN)System?
platform_adminแอดมินแพลทฟอร์มPlatform AdminSYSTEM
org_adminแอดมินบริษัทฯOrganize AdminSYSTEM
pmผู้จัดการโครงการProject ManagerCUSTOM
qc_mgrผู้จัดการตรวจสอบคุณภาพQC ManagerCUSTOM
qc_inspผู้ตรวจสอบคุณภาพQC InspectorCUSTOM
subผู้แก้ไขงาน/ผู้รับเหมาช่วงOP/Sub ContractorCUSTOM
ceoผู้บริหารCEOCUSTOM

26 Modules · grouped

  • ภาพรวม:m_dash
  • งานหลัก · โครงการ/ไซต์:m_proj · m_doc · m_wbs
  • งานหลัก · ตรวจสอบคุณภาพ:m_task · m_check · m_wf
  • งานหลัก · NCR:m_ncr · m_rpt
  • ตั้งค่า · ฉัน:m_profile · m_co · m_bill · m_notif · m_lang
  • ตั้งค่า · Master Data:m_menu · m_pkg · m_rbac · m_usr · m_addr · m_dept · m_btype · m_ptype · m_dtype · m_terms · m_pdpa · m_audit

5 Actions + Permission Encoding

CodeLetterRCEDAคำอธิบาย
FFullFull Access
4RCEDR/C/E/D (no Approve)
3RCER/C/E (no Delete/Approve)
VViewRead only
NNoneNo access · ซ่อนเมนู

PERM_MATRIX (Typescript)

// src/lib/rbac/matrix.ts
export type PermCode = 'F' | '4' | '3' | 'V' | 'N';
export const ROLE_INDEX: Record<RoleCode, number> = {
  platform_admin: 0, org_admin: 1, pm: 2, qc_mgr: 3, qc_insp: 4, sub: 5, ceo: 6
};

// Order: [platform_admin, org_admin, pm, qc_mgr, qc_insp, sub, ceo]
export const PERM_MATRIX: Record<ModuleId, PermCode[]> = {
  m_dash:    ['F','F','F','F','4','4','4'],
  m_proj:    ['F','F','F','N','N','N','N'],
  m_check:   ['F','F','V','F','N','N','N'],
  m_rpt:     ['4','4','4','4','3','3','3'],
  m_terms:   ['4','N','N','N','N','N','N'],
  m_audit:   ['4','N','N','N','N','N','N'],
  // ... full 26 modules
};

export const EXPANDED_PERMS: Record<PermCode, Record<ActionCode, boolean>> = {
  F: { view:true, create:true, edit:true, delete:true, approve:true },
  '4': { view:true, create:true, edit:true, delete:true, approve:false },
  '3': { view:true, create:true, edit:true, delete:false, approve:false },
  V: { view:true, create:false, edit:false, delete:false, approve:false },
  N: { view:false, create:false, edit:false, delete:false, approve:false }
};
4

🖼️ Page Layout (Header + Roles + Matrix)

3 sections บนหน้าเดียว · responsive grid 12 columns

UI Mockup · Full Page

ตั้งค่า / ข้อมูลตั้งต้น / 🔐 บทบาทและสิทธิ์การเข้าถึง จัดการ 7 บทบาท × 26 โมดูล · เพิ่ม Custom Role · ตั้งสิทธิ์ R/C/E/D/A + สร้างบทบาทใหม่ 📋 รายการบทบาท (7) แอดมินแพลทฟอร์ม platform_admin · SYSTEM SYS แอดมินบริษัทฯ org_admin · SYSTEM SYS ผู้จัดการโครงการ pm · Custom ผู้จัดการตรวจสอบคุณภาพ qc_mgr · Custom ⋮ qc_insp · sub · ceo ⋮ + เพิ่มบทบาทใหม่ 🛡️ เมทริกซ์สิทธิ์ · ผู้จัดการโครงการ 26 โมดูล × 5 การกระทำ · R = ดู · C = สร้าง · E = แก้ไข · D = ลบ · A = อนุมัติ 📤 Export ✎ แก้ไขสิทธิ์ 📊 ภาพรวม แดชบอร์ด (m_dash) R C E D A 🏗️ งานหลัก · โครงการ/ไซต์ จัดการโครงการ (m_proj) จัดการเอกสาร (m_doc) จัดการ WBS (m_wbs) ⋮ (rest of 26 modules · sectioned · scrollable) ⋮ ตัวย่อ: R = Read (ดู) · C = Create (สร้าง) · E = Edit (แก้ไข) · D = Delete (ลบ) · A = Approve (อนุมัติ) · 🟢 มีสิทธิ์ · ⚪ ไม่มีสิทธิ์
รูป 4.1 · Layout 12-col: Roles list (3 col) · Permission Matrix (9 col) · มี Add Role button มุมขวาบน + sectioned matrix

Layout Grid

<div className="grid grid-cols-12 gap-6">
  <aside className="col-span-3">
    <RolesList />
  </aside>
  <main className="col-span-9">
    <PermissionMatrix roleId={selectedRoleId} />
  </main>
</div>
5

📋 Roles List Section

รายการบทบาท 7 + custom · click → load matrix · มี SYS badge สำหรับ system role

Component Breakdown

ComponentTypeBehavior
<RolesList />ClientMap roles → role card
<RoleCard />ClientonClick → setSelectedRoleId · highlight active
<AddRoleButton />Clientopen RoleModal mode='create'
<SystemBadge />ClientShow "SYS" pill for platform/org admin

API Calls

📡 Initial fetch (Server Component)
GET/api/rolesรายการ roles ทั้งหมด (system + custom) ของ org

Critical Actions

// Zustand store
export const useRBACStore = create<State>((set) => ({
  selectedRoleId: 'platform_admin',                     // default
  setSelectedRoleId: (id: string) => set({ selectedRoleId: id })
}));

// RoleCard click handler
const selectRole = (id: string) => {
  setSelectedRoleId(id);
  analytics.track('rbac.role_selected', { role_id: id });
};

Edit / Delete (System role protection)

const handleEdit = (role: Role) => {
  if (role.is_system) {
    toast.warning(t('rbac.systemRole.cannotEdit'));
    // ⚠️ ยังเปิด modal ได้ — แต่ name + code disabled
  }
  openRoleModal({ mode: 'edit', role });
};

const handleDelete = (role: Role) => {
  if (role.is_system) {
    toast.error(t('rbac.systemRole.cannotDelete'));
    return;
  }
  openDeleteConfirm(role);
};
🛡️
System roles protection: platform_admin + org_admin ห้ามแก้ code และห้ามลบ · ตรวจซ้ำใน Server Action ก่อน DB write
6

🛡️ Permission Matrix Section

26 modules grouped · 5 action checkboxes · view-only mode + edit mode

UI Mockup · Matrix Row

⚠️ NCR · รายงานไม่สอดคล้อง (2 modules) จัดการ NCR m_ncr R C E D A (code: 3+A) รายงาน m_rpt R C E D A code: 4 💡 Tip: Hover ที่ checkbox จะแสดง tooltip · Click toggle checkbox · ปุ่ม "แก้ไขสิทธิ์" เพื่อเข้า edit mode Read-only mode: ปิดทุก checkbox ป้องกันการแก้ไขโดยไม่ตั้งใจ
รูป 6.1 · Permission matrix row · 5 checkboxes (R/C/E/D/A) · section header teal-bg · alternating row colors

PermissionMatrix Component (TSX)

'use client';
import { useState, useTransition } from 'react';
import { updatePermissionAction } from '../actions';

interface Props {
  roleId: string;
  modules: Module[];
  permissions: Permission[];
  editMode: boolean;
}

export function PermissionMatrix({ roleId, modules, permissions, editMode }: Props) {
  const [optimistic, setOptimistic] = useState(permissions);
  const [pending, startTransition] = useTransition();

  const grouped = groupBySection(modules);

  const handleToggle = (moduleId: string, action: ActionCode, checked: boolean) => {
    // 1) Optimistic UI
    setOptimistic(prev => applyToggle(prev, roleId, moduleId, action, checked));

    // 2) Server Action
    startTransition(async () => {
      try {
        await updatePermissionAction({ roleId, moduleId, action, granted: checked });
      } catch (e) {
        setOptimistic(permissions);                          // rollback
        toast.error(t('rbac.updateFailed'));
      }
    });
  };

  return (
    <div className="divide-y divide-ink-100">
      {Object.entries(grouped).map(([section, mods]) => (
        <section key={section}>
          <div className="bg-teal-bg px-4 py-2 font-bold text-teal-dark">{section}</div>
          {mods.map(m => (
            <ModuleRow key={m.id} module={m} permissions={optimistic}
              roleId={roleId} editMode={editMode} onToggle={handleToggle} />
          ))}
        </section>
      ))}
    </div>
  );
}

API Calls

📡 Initial fetch · cached per role
GET/api/modules26 modules พร้อม section + i18n names
GET/api/roles/{id}/permissionsPermissions ของ role · เป็น list of {module_id, actions[]}
PATCH/api/roles/{id}/permissionsUpdate permissions (bulk) · audit log auto

Display Mode (View vs Edit)

  • View mode (default):Checkboxes disabled · ใช้สีเขียวสำหรับ ✓ · gray dot สำหรับ ✕
  • Edit mode:กดปุ่ม "แก้ไขสิทธิ์" → checkbox enabled · save แต่ละการกดผ่าน PATCH · optimistic UI
  • Code badge:แสดง encoded code (F/4/3/V/N) ด้านขวาของแต่ละแถวเพื่อ debug
7

📝 Create / Edit Role Modal

Dialog สำหรับสร้าง/แก้ไข role · มีฟิลด์: ชื่อ TH/EN · code (auto/manual) · description · color · is_system (readonly)

UI Mockup

📝 สร้างบทบาทใหม่ กรอกข้อมูลและกำหนดสิทธิ์เริ่มต้น ชื่อบทบาท (TH) * 🇹🇭 TH เช่น ผู้ตรวจสอบงานสนาม ชื่อบทบาท (EN) * 🇬🇧 EN e.g. Field Inspector รหัสบทบาท (Code) * Auto Manual field_inspector 🔒 ระบบสร้างจากชื่อ EN อัตโนมัติ · เปลี่ยนเป็น Manual ถ้าต้องการกำหนดเอง คำอธิบาย บทบาทนี้ทำหน้าที่ตรวจสอบและรายงานปัญหาในสนาม... ยกเลิก บันทึก
รูป 7.1 · Create Role Modal · มี TH/EN field · Code toggle Auto/Manual · readonly code field ใน Auto mode

Zod Schema

// schema.ts
export const RoleSchema = z.object({
  name_th: z.string().min(2, t('rbac.nameTooShort')).max(50),
  name_en: z.string().min(2).max(50),
  code: z.string()
    .regex(/^[a-z][a-z0-9_]{1,29}$/, t('rbac.codeFormat')),
  code_auto: z.boolean().default(true),
  description: z.string().max(500).optional(),
  color: z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
  is_system: z.boolean().default(false)
});

export type RoleForm = z.infer<typeof RoleSchema>;

Critical Actions

📡 Role mutations
POST/api/rolesCreate custom role · เริ่มต้นไม่มี permissions
PATCH/api/roles/{id}Update name · description · color (NOT code ถ้า system)
DELETE/api/roles/{id}Soft delete (deleted_at) · เฉพาะ non-system

Server Action (Create)

'use server';

export async function createRoleAction(formData: RoleForm) {
  await requirePerm('m_rbac', 'create');

  const parsed = RoleSchema.safeParse(formData);
  if (!parsed.success) throw new ValidationError(parsed.error);

  const idempotencyKey = crypto.randomUUID();

  try {
    const role = await api.post('/api/roles', parsed.data, {
      headers: { 'X-Idempotency-Key': idempotencyKey }
    });
    revalidatePath('/setting/master/rbac');
    return { ok: true, role };
  } catch (e) {
    if (e.code === 'DUPLICATE_CODE') {
      return { ok: false, error: t('rbac.duplicateCode') };
    }
    throw e;
  }
}
8

🔄 Auto/Manual Code Switch

Toggle ระหว่างการสร้าง code อัตโนมัติจากชื่อ EN และการกรอกเอง

Behavior

  • Auto mode (default):Code generated อัตโนมัติจาก name_en · readonly input · slugify + lowercase + replace spaces with underscore
  • Manual mode:User พิมพ์ code เอง · validate regex ^[a-z][a-z0-9_]{1,29}$
  • System roles:Code disabled ทั้ง mode · ห้ามแก้

Implementation (RHF + watch)

const { watch, setValue, register, formState } = useForm<RoleForm>({
  resolver: zodResolver(RoleSchema),
  defaultValues: { code_auto: true, is_system: false }
});

const nameEn = watch('name_en');
const codeAuto = watch('code_auto');
const isSystem = watch('is_system');

// Auto-generate code when name_en changes (only in auto mode)
useEffect(() => {
  if (codeAuto && nameEn) {
    const generated = nameEn
      .toLowerCase()
      .trim()
      .replace(/[^a-z0-9\s]/g, '')
      .replace(/\s+/g, '_')
      .slice(0, 30);
    setValue('code', generated, { shouldValidate: true });
  }
}, [nameEn, codeAuto, setValue]);

// Code input
<input
  {...register('code')}
  readOnly={codeAuto || isSystem}
  className={cn('input', (codeAuto||isSystem) && 'bg-ink-50 text-ink-500')}
/>
💡
UX detail: Toggle slider ใช้ rounded pill style · เมื่อสลับจาก Manual → Auto จะคำนวณ code ใหม่ทันที (เตือนถ้า manual value เปลี่ยน)
9

🛡️ System Roles Protection

platform_admin + org_admin มี constraint พิเศษ — ห้ามแก้ code · ห้ามลบ · ห้ามลด permissions ลงต่ำกว่า baseline

Constraints

ConstraintFrontend BehaviorBackend Enforcement
ห้ามแก้ codeInput disabled · explanatory tooltipPATCH /api/roles/{id} reject ถ้า is_system + code changes
ห้ามลบ roleDelete button hidden · DELETE returns 403DELETE /api/roles/{id} reject 403
ห้ามลด platform_admin permissionsCheckbox locked (disabled) สำหรับทุก actionPATCH /api/roles/.../permissions reject ถ้าจะ unset
org_admin: ห้ามแก้ในระดับ platformเฉพาะ platform_admin ผู้ใช้ที่เห็นRLS · scope by organization_id

Backend Guard (Go)

func UpdateRoleHandler(w http.ResponseWriter, r *http.Request) {
  roleID := chi.URLParam(r, "id")
  role, err := repo.GetRole(roleID)
  if err != nil { http.Error(w, "404", 404); return }

  var input RoleUpdateInput
  json.NewDecoder(r.Body).Decode(&input)

  // System role guard
  if role.IsSystem {
    if input.Code != nil && *input.Code != role.Code {
      http.Error(w, "FORBIDDEN: cannot change system role code", 403)
      return
    }
  }

  // Org scope (non-platform_admin)
  if ctx.Role != "platform_admin" && role.OrganizationID != ctx.OrgID {
    http.Error(w, "FORBIDDEN: cross-org", 403)
    return
  }

  updated, err := repo.UpdateRole(roleID, input)
  // AuditMiddleware logs before/after automatically
}
10

📡 Server Actions & API Endpoints

RBAC-related API ทั้งหมด · ~14 endpoints

Full API Catalog

📡 Roles
GET/api/rolesList roles (system + custom) · filter by org
GET/api/roles/{id}Single role detail
POST/api/rolesCreate custom role · X-Idempotency-Key required
PATCH/api/roles/{id}Update role · system roles reject code change
DELETE/api/roles/{id}Soft delete · non-system only · cascade users to ceo
POST/api/roles/{id}/duplicateClone role with new code · permissions copied
📡 Modules & Permissions
GET/api/modules26 modules · grouped by section
GET/api/permissionsFull matrix (roles × modules × actions)
GET/api/roles/{id}/permissionsSingle role's permissions
PATCH/api/roles/{id}/permissionsBulk update permissions · audit log
POST/api/roles/{id}/permissions/copyCopy from another role's permissions
📡 User Assignment (related)
GET/api/roles/{id}/usersUsers assigned to this role · count
PATCH/api/users/{id}/roleChange user's role · audit log

PATCH Permissions Payload

// PATCH /api/roles/{id}/permissions
{
  "permissions": [
    { "module_id": "m_ncr", "actions": ["view", "create", "edit", "approve"] },
    { "module_id": "m_rpt", "actions": ["view", "create", "edit", "delete"] }
  ]
}

// Response: 200 OK + updated permissions list
11

🌊 Data Flow Diagram (DFD)

การไหลของข้อมูลระหว่าง User · Frontend · Backend · DB · Audit
RBAC Settings · Data Flow Diagram (Level 1) 👤 Admin (platform/org) ⚛️ Frontend (Next.js) RBAC Settings Page RolesList (Zustand) PermissionMatrix (optimistic) RoleModal (RHF + Zod) ✅ Server-side requirePerm 🛡️ Client-side hasPerm() 🦫 Backend API (Go-Chi) /api/roles · /modules /api/permissions RequirePerm middleware System role guard AuditMiddleware (before/after) Idempotency-Key cache 🐘 PG 15 roles modules permissions user_roles audit_logs organizations 🔍 Audit Trail Pipeline ① AuditMiddlewareCapture before/after on PATCH/POST/DELETE ② Goroutine + bufferAsync write · non-blocking ③ audit_logs INSERTPG trigger blocks UPDATE/DELETE ④ ImmutableRead-only interact REST SQL async audit RBAC Workflow: ① Admin เปิดหน้า → Server fetch initial (requirePerm m_rbac.view) → ② Admin click role → load matrix ③ Admin toggle checkbox → optimistic UI + Server Action → ④ PATCH /api/roles/.../permissions ⑤ Backend: RequirePerm m_rbac.edit → System guard → DB UPDATE → ⑥ AuditMiddleware async log
12

🔄 State Sequence Diagram

State transitions ของหน้า + modal lifecycle
○ Page Load /setting/master/rbac 📋 View Mode show matrix · checkboxes disabled (read-only) ✎ Edit Mode checkboxes enabled PATCH optimistic ⏳ Saving useTransition pending spinner on checkbox ✅ Saved toast success audit logged ❌ Error rollback optimistic toast.error 📝 Modal Closed default state 📝 Modal Open + Add / ✎ Edit RHF init 📝 Filling code_auto computed Zod validate ⏳ Submitting POST /api/roles Idempotency-Key 🛡️ System Role code/delete blocked tooltip warning on error ⚙️ Transitions: View → Edit: click "แก้ไขสิทธิ์" · Edit → Saving: toggle checkbox · Saving → Saved: API 200 · Saving → Error: API 4xx/5xx Modal: Closed → Open (click + / ✎) · Open → Filling (user input) · Filling → Submitting (click บันทึก) · Submitting → Closed (success)
13

✅ Validation Reference

Zod schemas + Backend constraints
FieldRuleError message (TH)
name_th2-50 ตัวอักษรชื่อ TH ต้องมี 2-50 ตัวอักษร
name_en2-50 chars · ASCIIชื่อ EN ต้องมี 2-50 ตัวอักษร (ASCII)
code^[a-z][a-z0-9_]{1,29}$รหัสต้องขึ้นต้นด้วย a-z · มี a-z 0-9 _ เท่านั้น · ยาว 2-30
code uniquenessunique per orgรหัสนี้มีอยู่แล้ว · กรุณาใช้รหัสอื่น
description≤ 500 chars · optionalคำอธิบายเกิน 500 ตัวอักษร
color#RRGGBB hex (optional)รหัสสีไม่ถูกต้อง
is_systemreadonly · server-controlled
System role code changerejected by backendไม่สามารถเปลี่ยนรหัส system role ได้
Delete role with assigned userscascade users to ceo or warnมีผู้ใช้ {n} คนใช้บทบาทนี้ · ต้องย้ายก่อนลบ
14

📓 Audit Log + Security

การบันทึก audit · immutable · ตาม CLAUDE.md §12 PII protection

Audit Events

ActionModuleAudit detail
Create rolem_rbacbefore: null · after: role JSON
Update role name/desc/colorm_rbacbefore/after diff
Update permissionsm_rbacbefore: array · after: array · module-level diff
Delete rolem_rbacbefore: role · after: { deleted_at: now }
System role change attempt (rejected)m_rbacaction: "blocked" · reason: SYSTEM_ROLE

PII Protection (PDPA)

  • ห้ามเก็บ:password_hash · tokens · gov ID ใน audit_log.before/after
  • Whitelist columns:code, name_th, name_en, description, color, is_system, permissions[]
  • Storage:audit_logs ใน PostgreSQL · PG trigger block UPDATE/DELETE
  • Retention:≥ 5 ปี ตาม PDPA + ประมวลรัษฎากร

Authorization Layers

1️⃣ Route guard

requirePerm('m_rbac', 'view') ใน page.tsx

2️⃣ UI hide

hasPerm() ซ่อน edit/delete buttons

3️⃣ Server Action

Re-check requirePerm() ใน action

4️⃣ Backend middleware

Go-Chi RequirePerm(module, action)

5️⃣ DB RLS

PostgreSQL Row-Level Security · org_id filter

15

✔️ Acceptance Criteria

Definition of Done · ก่อน merge ต้องผ่านทั้งหมด
#เกณฑ์Verify by
1หน้า RBAC ครอบ AppShell · ใช้ Sidebar canonicalManual + Playwright
2เข้าถึงได้เฉพาะ platform_admin + org_admin · บทบาทอื่น 403RBAC integration test
3Roles list แสดง 7 บทบาท + custom · SYS badge สำหรับ systemVisual + content check
4Matrix แสดง 26 modules grouped 6 sectionsVisual + data check
5View mode: checkboxes disabled · Edit mode: enabledWidget test
6Toggle checkbox → optimistic UI + PATCH successIntegration test
7System role: code field disabled · delete button hiddenManual + e2e
8Auto code: type name_en → code generated · slug formatWidget test
9Manual code mode: ต้องผ่าน regex ^[a-z][a-z0-9_]{1,29}$Form validation test
10Duplicate code → 409 + toast แสดง "รหัสนี้มีอยู่แล้ว"API test
11Delete role with users → blocked + warning toastAPI test
12Audit log บันทึกทุก write · ไม่มี PIIBackend integration test
13TH/EN toggle ทำงานทุก section + matrixi18n test
14POST/PATCH มี X-Idempotency-KeyInterceptor test
15Performance: matrix render 26×5 = 130 cells ≤ 100msLighthouse